Performance pass: Tiers A/B/C (visual-safe)#330
Merged
Merged
Conversation
Tier A — visual-safe runtime wins: - Swap all 21 collectAsState() → collectAsStateWithLifecycle() so screens stop recomposing while backgrounded/covered. - Dashboard: pause the 5s status poll when the screen isn't STARTED (LifecycleStartEffect + resume/pauseAutoRefresh), so it no longer polls the network or churns state off-screen. - StatsRepository.getStats: run off Dispatchers.IO and fan the three independent groups (quick/deep/sync) out with async instead of ~50 sequential DAO queries on the main thread. - Add contentType to the charges/drives/trips/mileage lists so Compose reuses row layout nodes during fling. - ChargingNotificationWorker: stop refetching the full car list once per car (pass the already-fetched CarData into checkCarStatus). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arts Tier B — visual-safe per-frame/allocation wins: - ChartDrawUtils text helpers (drawYAxisLabels/drawDualYAxisLabels/ drawTimeLabels) now take a pre-built android.graphics.Paint instead of allocating one per draw. OptimizedLineChart/DualAxisLineChart build the paints once with remember(color). The 800ms entrance animation redraws ~48 frames, so this removes ~3-5 Paint (+Color/String) allocations per frame per chart. Same pixels. - PowerSocOverlayChart: sort each curve's points by SoC once (remember) and pre-order curves by base, instead of re-sorting inside the draw loop and twice per crosshair-drag frame via interpolatePower. Same curves. - MileageScreen: memoize the yearly/monthly/daily chart-data derivation and the inner BarChartData mapping with remember, and mark BarChartData/ BarSegment @immutable so InteractiveBarChart can skip. Deliberately skipped two low-value Tier B items: caching the widget progress-bar bitmap (updates are 3-min and each carries a new battery level, so a cache almost never hits) and caching AppSettings in SettingsDataStore (negligible gain over the already in-memory-cached DataStore, and a stale server URL/token would silently break API calls). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ute simplify Tier C — data-layer wins (visual-safe; correctness preserved): - ChargeComparisonRepository/DriveComparisonRepository.findComparable: wrap the full-history load + per-row Haversine + sort in withContext(Dispatchers.Default) so it no longer runs on the caller's main thread. (Room already runs the queries on its own executor.) - TripRepository.getTrips: run off Dispatchers.Default, and only run the beta-era duplicate-cleanup (SHA-256 per saved trip) once per car per session instead of on every trip-list open. - SyncRepository.syncChargeDetails: preload charge summaries once (getAllForCar) instead of a per-charge DB read inside the batch loop. - RouteSimplifier: rewrite Douglas-Peucker iteratively with a keep[] bitset + explicit stack — same output, no per-level list allocation, bounded stack (was O(n) recursion depth + list rebuild per level). Deferred (correctness surface): pushing the comparison bounding-box prefilter into SQL — the Kotlin Haversine already gates matches, but a too-tight SQL box would silently drop valid comparisons, so it needs a new indexed query + margin care. Moving the work off-main removes the actual jank without that risk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runtime performance work from the perf audit — Tiers A, B, C, all visual-safe. No pixels change (the one exception is a deliberate cost thousands-separator carried over from the prior PR's CHANGELOG entry). Build was already modern (Kotlin 2.3.20 strong-skipping, release minify/shrink), so these target hot paths.
Tier A — tiny change, big gain
collectAsState→collectAsStateWithLifecycleapp-wide (21 screens): stop recomposing while backgrounded/covered.LifecycleStartEffect+ resume/pause) — no off-screen network/CPU/battery.StatsRepository.getStatsruns offDispatchers.IOand fans its 3 independent groups out withasyncinstead of ~50 sequential DAO queries on the main thread.contentTypeon the charges/drives/trips/mileage lists — row-node reuse during fling.ChargingNotificationWorkerno longer refetches the full car list once per car.Tier B — per-frame / allocation wins
Paints inChartDrawUtils(built once viaremember, not per draw) — the 800ms entrance animation redraws ~48 frames, so this removes thousands of transientPaint/Color/Stringallocations. Same pixels.PowerSocOverlayChartpre-sorts each curve's points once instead of re-sorting inside the draw loop and twice per crosshair-drag frame.BarChartDatamapping;BarChartData/BarSegmentmarked@Immutable.Tier C — data layer
findComparable) move the full-history load + per-row Haversine + sort toDispatchers.Default(off the main thread).TripRepository.getTripsruns off-main, and the beta-era SHA-256 duplicate cleanup now runs once per car per session, not every open.SyncRepository.syncChargeDetailspreloads summaries once instead of a per-charge DB read in the batch loop.RouteSimplifierrewritten iteratively (keep[] bitset + explicit stack) — same output, no per-level list allocation, bounded stack.Deliberately skipped / deferred (engineering judgment, flagged not silently dropped)
AppSettingscache (Tier B): negligible gain over the already in-memory-cached DataStore, and a stale server URL/token would silently break API calls.:baselineprofilemacrobenchmark module (half-day of scaffolding). Happy to do it as a follow-up. (F-Droid note: the generatedbaseline-prof.txtships fine as a source file; only regeneration is local, not in F-Droid CI.)Verification
./gradlew testDebugUnitTest lintDebug assembleDebug— all green.🤖 Generated with Claude Code